/*
 * Copyright (c) 2022, SHANGHAI FUDAN MICROELECTRONICS GROUP CO., LTD.(FUDAN MICROELECTRONICS./ FUDAN MICRO.)
 * All rights reserved.
 *
 * Processor:                   FM33LE0xxA
 * http:                        http://www.fmdevelopers.com.cn/
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * 4. To provide the most up-to-date information, the revision of our documents 
 *    on the World Wide Web will be the most Current. Your printed copy may be  
 *    an earlier revision. To verify you have the latest information avaliable,
 *    refer to: http://www.fmdevelopers.com.cn/.
 *
 * THIS SOFTWARE IS PROVIDED BY FUDAN MICRO "AS IS" AND ANY EXPRESSED 
   ORIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
   OF MERCHANTABILITY NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED.IN NO EVENT SHALL FUDAN MICRO OR ITS CONTRIBUTORS BE LIABLE 
   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISINGIN ANY WAY OUT OF THE 
   USE OF THIS SOFTWARE, EVEN IF ADVISED OFTHE POSSIBILITY OF SUCH DAMAGE.
*/
#include "pll.h"

#define PLL_TIMEOUT            0x00000FFFU  /* 约90ms */ 
#define XTHF_TIMEOUT           0x0003FFFFU  /* 约10s */

/* XTHF寄存器参数检查 */
#define XTHF_REG_CHECK(xthf)  ((FL_RCC_XTHF_ReadDriverStrength() == (xthf)->DriverStrength) &&\
                               (FL_RCC_XTHF_IsEnabled()          == FL_ENABLE))

/* 系统时钟选择错误变量 */
volatile uint32_t SYSCLKSELErrorState = 0;
/* 系统时钟状态变量 */
volatile SYSCLK_STATE SYSCLKState = SYSCLK_NORMAL;



/* RCHF时钟参数配置 */
const FL_RCHF_InitTypeDef RCHF_InitParameter = {
    
    /* RCHF时钟频率 可配置为8M,16M,24M*/
    FL_RCC_RCHF_FREQUENCY_16MHZ
    
};



/* XTHF时钟参数配置 */
const FL_XTHF_InitTypeDef XTHF_InitParameter = {
    
    /* XTHF晶振频率 16M */
    16000000,
  
    /* XTHF振荡强度 推荐值为0x07 */
    0x07
};


/* PLL时钟参数配置 */
const FL_PLL_InitTypeDef PLL_InitParameter = {

    /* PLL倍频比 倍频至32M */
    32,
  
    /* PLL参考时钟预分频，将 16M 时钟源分频至 1M */
    FL_RCC_PLL_PSC_DIV16,
  
    /* PLL时钟源输入选择 */
    FL_RCC_PLL_CLK_SOURCE_XTHF
};


/**
  * @brief  停振监测中断服务函数
  * @param  void
  * @retval void
  */
void HFDET_IRQHandler(void)
{
    /* 停振检测中断处理 */
    if((FL_FDET_IsEnabledIT_XTHFFail() == 0x01UL)
    && (FL_FDET_IsActiveFlag_XTHFFail() == 0x01UL))
    {
        /* 清XTHF停振标识和PLL失锁标识在下面函数内执行 */
        SwtichToSenondarySystemClock(&RCHF_InitParameter, &PLL_InitParameter);
        
        /* 更换系统时钟错误标志 */
        SYSCLKState = SYSCLK_ERROR;
    }
}


/**
  * @brief
  * @rmtoll   PLLCR    OSEL    LL_RCC_GetPLLDigitalDomainOutput
  * @param    Source This parameter can be one of the following values:
  *           @arg @ref FL_RCC_PLL_CLK_SOURCE_RCHF
  *           @arg @ref FL_RCC_PLL_CLK_SOURCE_XTHF
  * @param    PLL_R 锁相环的参考时钟需要预分频至1M，再进行PLL倍频 This parameter can be one of the following values:
  *           @arg @ref FL_RCC_PLL_PSC_DIV1
  *           @arg @ref FL_RCC_PLL_PSC_DIV2
  *           @arg @ref FL_RCC_PLL_PSC_DIV4
  *           @arg @ref FL_RCC_PLL_PSC_DIV8
  *           @arg @ref FL_RCC_PLL_PSC_DIV12
  *           @arg @ref FL_RCC_PLL_PSC_DIV16
  *           @arg @ref FL_RCC_PLL_PSC_DIV24
  *           @arg @ref FL_RCC_PLL_PSC_DIV32
  * @param    PLL_DB This parameter can be Between parameters of the following values:
  *           @arg @ref

  * @param    PLL_O This parameter can be one of the following values:
  *           @arg @ref FL_RCC_PLL_OUTPUT_X1
  *           @arg @ref FL_RCC_PLL_OUTPUT_X2
  * @retval   None
  */
static void RCC_PLL_ConfigDomain_SYS(uint32_t Source, uint32_t PLL_R, uint32_t PLL_DB, uint32_t PLL_O)
{
    MODIFY_REG(RCC->PLLCR, RCC_PLLCR_DB_Msk | RCC_PLLCR_REFPRSC_Msk | RCC_PLLCR_OSEL_Msk | RCC_PLLCR_INSEL_Msk,
               ((PLL_DB - 1 )<< RCC_PLLCR_DB_Pos) | PLL_R | PLL_O | Source);
}


/**
  * @brief  内部RCHF初始化函数
  * @param  clock 目标频率
  * @retval none
  */

static uint32_t RCHFInit(uint32_t clock)
{
    /* PLL分频到1M的参量 */
    uint32_t PLL_Prescaler_1M;
    switch(clock)
    {
        case FL_RCC_RCHF_FREQUENCY_8MHZ:
            FL_RCC_RCHF_WriteTrimValue(RCHF8M_TRIM);
            PLL_Prescaler_1M = FL_RCC_PLL_PSC_DIV8;
            break;

        case FL_RCC_RCHF_FREQUENCY_16MHZ:
            FL_RCC_RCHF_WriteTrimValue(RCHF16M_TRIM);
            PLL_Prescaler_1M = FL_RCC_PLL_PSC_DIV16;
            break;

        case FL_RCC_RCHF_FREQUENCY_24MHZ:
            FL_RCC_RCHF_WriteTrimValue(RCHF24M_TRIM);
            PLL_Prescaler_1M = FL_RCC_PLL_PSC_DIV24;
            break;

        default:
            FL_RCC_RCHF_WriteTrimValue(RCHF8M_TRIM);
            PLL_Prescaler_1M = FL_RCC_PLL_PSC_DIV8;
            break;
    }

    FL_RCC_RCHF_SetFrequency(clock);
    FL_RCC_RCHF_Enable();
    return PLL_Prescaler_1M;
}


/**
  * @brief  选择内部RCHF作为锁相环的参考时钟
  *
  * @note   锁相环的参考时钟需要预分频至1M，再进行PLL倍频
  * @param  rchf  RCHF参考时钟选择    This parameter can be one of the following values:    
  *           @arg @ref FL_RCC_RCHF_FREQUENCY_8MHZ
  *           @arg @ref FL_RCC_RCHF_FREQUENCY_16MHZ
  *           @arg @ref FL_RCC_RCHF_FREQUENCY_24MHZ
  *           @arg @ref FL_RCC_RCHF_FREQUENCY_32MHZ
  * @param  clock PLL倍频输出频率
  * @note   输出时钟频率为1M*（clock+1），最高输出频率可达64M
  * @retval none
  */

    /*pll时钟范围为32M~64M，超过则返回异常*/
    
    /* 初始化RCHF */
    
    /* 配置读等待周期为2CYCLE */
    
    /* 设置系统时钟参数 时钟源 参考时钟预分频系数 倍频比 */
    
    /* 使能PLL */
    
    /* 等待PLL建立稳定，才能将系统时钟源切到PLL，如没有建立，则进入超时 */
    
    /* 设置 AHB APB1 APB2 总线时钟分频 */
    
    /* 设置系统时钟源 */

    /* 根据系统时钟配置FLASH读等待周期 */




/**
  * @brief  外部XTHF初始化函数
  * @param  Strength 晶振驱动强度参数
  * @retval none
  */

static void XTHFInit(uint32_t Strength)
{
    FL_GPIO_InitTypeDef      gpioInitStruction;
    FL_NVIC_ConfigTypeDef    InterruptConfigStruct;

    /* PC2.3配置成模拟功能，外接XTHF */
    gpioInitStruction.mode       = FL_GPIO_MODE_ANALOG;
    gpioInitStruction.outputType = FL_GPIO_OUTPUT_PUSHPULL;
    gpioInitStruction.pull       = FL_DISABLE;

    gpioInitStruction.pin = FL_GPIO_PIN_2 | FL_GPIO_PIN_3;
    FL_GPIO_Init(GPIOC, &gpioInitStruction);

    /* 配置驱动强度 */
    FL_RCC_XTHF_WriteDriverStrength(Strength);
    FL_RCC_XTHF_Enable();
    FL_DelayMs(2);

    /* 使能停振检测中断 */
    FL_FDET_ClearFlag_XTHFFail();
    FL_FDET_EnableIT_XTHFFail();

    InterruptConfigStruct.preemptPriority = 0;
    FL_NVIC_Init(&InterruptConfigStruct,HFDET_IRQn);
}


/**
  * @brief  选择外部XTHF作为锁相环的参考时钟
  *           
  * @param  clock PLL倍频输出频率
  * @note   输出时钟频率为1M*（clock+1），最高输出频率可达64M
  * @retval none
  */
uint32_t SelXTHFToPLL(const FL_XTHF_InitTypeDef *XTHFInitParameter ,const FL_PLL_InitTypeDef *PLLInitParameter)
{
    uint32_t counter = 0;
    uint32_t readystatus = 0;
  
    /*pll时钟范围为32M~64M，超过则返回异常*/
    if((PLLInitParameter->Multiplier < 32U) || (PLLInitParameter->Multiplier > 64U))
    {
        return 1;
    }
    
    /* XTHF初始化使能配置 */
    XTHFInit(XTHFInitParameter->DriverStrength);
    
    /* 设置FLASH等待周期为2CYCLE */
    FL_FLASH_SetReadWait(FLASH, FL_FLASH_READ_WAIT_2CYCLE);
    
    /* 设置系统时钟参数 时钟源 参考时钟预分频系数 倍频比 */
    RCC_PLL_ConfigDomain_SYS(PLLInitParameter->ClockSource, PLLInitParameter->Prescaler, PLLInitParameter->Multiplier, FL_RCC_PLL_OUTPUT_X1);
    
    /* 使能PLL */ 
    FL_RCC_PLL_Enable();

    /* 等待PLL建立稳定，才能将系统时钟源切到PLL，如没有建立，则进入超时 */
    do
    {
        readystatus = FL_RCC_IsActiveFlag_PLLReady();
        counter++;
    }
    while(( readystatus != 0x1U) && (counter != PLL_TIMEOUT));        
    
    /* 设置 AHB APB1 APB2 总线时钟分频 */
    FL_RCC_SetAHBPrescaler(FL_RCC_AHBCLK_PSC_DIV1);
    FL_RCC_SetAPB1Prescaler(FL_RCC_APB1CLK_PSC_DIV1);
    FL_RCC_SetAPB2Prescaler(FL_RCC_APB2CLK_PSC_DIV1);

    /* 设置系统时钟源 */
    FL_RCC_SetSystemClockSource(FL_RCC_SYSTEM_CLK_SOURCE_PLL);
    
    /* 根据系统时钟配置FLASH读等待周期 */
    if(PLLInitParameter->Multiplier <= 24)
    {
        FL_FLASH_SetReadWait(FLASH, FL_FLASH_READ_WAIT_0CYCLE);
    }
    else if((PLLInitParameter->Multiplier > 24) && (PLLInitParameter->Multiplier <= 48))
    {
        FL_FLASH_SetReadWait(FLASH, FL_FLASH_READ_WAIT_1CYCLE);
    }
    else
    {
        FL_FLASH_SetReadWait(FLASH, FL_FLASH_READ_WAIT_2CYCLE);
    }

    SystemCoreClockUpdate();
    return 0;
}



/**
  * @brief  切换至次系统时钟配置
  *           
  * @param  *RCHFInitParameter RCHF配置，需和XTHF参数相匹配
  * @param  *PLLInitParameter  PLL 配置
  * @retval none
  */
uint32_t SwtichToSenondarySystemClock(const FL_RCHF_InitTypeDef *RCHFInitParameter,
                                  const FL_PLL_InitTypeDef  *PLLInitParameter)
{
    uint32_t counter, PLL_Prescaler_1M;
    if((PLLInitParameter->Multiplier < 32U) || (PLLInitParameter->Multiplier > 64U))
    {
        return 1;
    }
    else
    {
    PLL_Prescaler_1M = RCHFInit(RCHFInitParameter->Frequence);

    /* 设置系统时钟源 */
    FL_RCC_SetSystemClockSource(FL_RCC_SYSTEM_CLK_SOURCE_RCHF);
  
    /* 禁能PLL */ 
    FL_RCC_PLL_Disable();
  
    /* 设置系统时钟参数 时钟源 参考时钟预分频系数 倍频比 */
    RCC_PLL_ConfigDomain_SYS(FL_RCC_PLL_CLK_SOURCE_RCHF, PLL_Prescaler_1M, PLLInitParameter->Multiplier, FL_RCC_PLL_OUTPUT_X1);
  
    /* 使能PLL */ 
    FL_RCC_PLL_Enable();
  
    counter = PLL_TIMEOUT;
    /* 等待PLL建立 */
    while((0x0 == FL_RCC_IsActiveFlag_PLLReady()) && (--counter));
        FL_RCC_SetAHBPrescaler(FL_RCC_AHBCLK_PSC_DIV1);
        FL_RCC_SetAPB1Prescaler(FL_RCC_APB1CLK_PSC_DIV1);
        FL_RCC_SetAPB2Prescaler(FL_RCC_APB2CLK_PSC_DIV1);
        FL_RCC_SetSystemClockSource(FL_RCC_SYSTEM_CLK_SOURCE_PLL);
        if(PLLInitParameter->Multiplier <= 24)
            FL_FLASH_SetReadWait(FLASH, FL_FLASH_READ_WAIT_0CYCLE);
        else if((PLLInitParameter->Multiplier > 24) && (PLLInitParameter->Multiplier <= 48))
            FL_FLASH_SetReadWait(FLASH, FL_FLASH_READ_WAIT_1CYCLE);
        else
            FL_FLASH_SetReadWait(FLASH, FL_FLASH_READ_WAIT_2CYCLE);
    }
  
    /* 清XTHF停振中断标识 */
    FL_FDET_ClearFlag_XTHFFail();
    FL_FDET_DisableIT_XTHFFail();
    
    /* 设置系统时钟源 */
    FL_RCC_SetSystemClockSource(FL_RCC_SYSTEM_CLK_SOURCE_PLL);
  
    SYSCLKState = SYSCLK_ERROR;
    SystemCoreClockUpdate();
    return 0;
}


/**
  * @brief  切换至主系统时钟配置
  *           
  * @param  *RCHFInitParameter RCHF配置
  * @param  *XTHFInitParameter XTHF配置
  * @param  *PLLInitParameter  PLL 配置
  * @retval none
  */
void SwtichToMainSystemClock(const FL_RCHF_InitTypeDef *RCHFInitParameter,
                             const FL_XTHF_InitTypeDef *XTHFInitParameter,
                             const FL_PLL_InitTypeDef  *PLLInitParameter)
{
    /* 监测是否发生时钟异常 */
    if ((SYSCLK_ERROR == SYSCLKState) &&\
       ((FL_RCC_SYSTEM_CLK_SOURCE_PLL != FL_RCC_GetSystemClockSource()) ||\
       (PLLInitParameter->ClockSource != FL_RCC_PLL_GetClockSource())))
    {
        /* 即使发生停振或失锁事件，XTHF寄存器参数不应发生变化 */
        if (XTHF_REG_CHECK(XTHFInitParameter) || XTHF_REG_CHECK(XTHFInitParameter))
        {
            volatile uint32_t Reday[3];  /* 避免编译器优化 */
            uint32_t counter, primask;
          
            /* 判断XTHF稳定标志是否建立，连续读三次 */
            Reday[0] = FL_RCC_XTHF_IsReady();
            Reday[1] = FL_RCC_XTHF_IsReady();
            Reday[2] = FL_RCC_XTHF_IsReady();
            if (Reday[0] && Reday[1] && Reday[2])
            {
                /* 清XTHF停振中断标识 */
                FL_FDET_ClearFlag_XTHFFail();
                if(0x0 == FL_FDET_IsActiveFlag_XTHFFail())
                {
                    /* 准备切换至主系统时钟 */
                    (void)RCHFInit(RCHFInitParameter->Frequence);
                    /* 竞态保护 */
                    primask = __get_PRIMASK();
                    __disable_irq();
                    /* 设置系统时钟源 */
                    FL_RCC_SetSystemClockSource(FL_RCC_SYSTEM_CLK_SOURCE_RCHF);
                    
                    /* 禁能PLL */ 
                    FL_RCC_PLL_Disable();
                  
                    /* 设置系统时钟参数 时钟源 参考时钟预分频系数 倍频比 */
                    RCC_PLL_ConfigDomain_SYS(PLLInitParameter->ClockSource, PLLInitParameter->Prescaler, PLLInitParameter->Multiplier, FL_RCC_PLL_OUTPUT_X1);
                  
                    /* 使能PLL */
                    FL_RCC_PLL_Enable();
                  
                    /* 等待PLL建立 */
                    counter = PLL_TIMEOUT;
                    do
                    {   /* 判断PLL锁住标志，连续读三次 */
                        Reday[0] = FL_RCC_IsActiveFlag_PLLReady();
                        Reday[1] = FL_RCC_IsActiveFlag_PLLReady();
                        Reday[2] = FL_RCC_IsActiveFlag_PLLReady();
                        if (Reday[0] && Reday[1] && Reday[2])
                        {
                            volatile uint32_t counter = 0;
                            uint32_t RCLP_Clock = (SystemCoreClock / 1000000) * 100;
                            for (counter = 0; counter < RCLP_Clock; counter++)
                            {
                                __NOP();
                            }
                            if (FL_FDET_IsActiveFlag_XTHFFail()) break; 

                            SYSCLKState = SYSCLK_NORMAL;
                            FL_FDET_EnableIT_XTHFFail();
                            FL_RCC_SetSystemClockSource(FL_RCC_SYSTEM_CLK_SOURCE_PLL);
                            SystemCoreClockUpdate();
                            break;
                        }
                    }while((0x0 == FL_FDET_IsActiveFlag_XTHFFail()) && (--counter));
                    
                    /* 未成功切换到主系统时钟，则恢复到次系统时钟 */
                    if (SYSCLK_NORMAL != SYSCLKState)
                    {
                        SwtichToSenondarySystemClock(&RCHF_InitParameter, &PLL_InitParameter);
                    }
                  
                    /* 取消竞态保护 */
                    __set_PRIMASK(primask);
                }
            }
        }
        else
        {
            (void)RCHFInit(RCHFInitParameter->Frequence);
            FL_RCC_SetSystemClockSource(FL_RCC_SYSTEM_CLK_SOURCE_RCHF);
            FL_RCC_PLL_Disable();
          
            /* 禁止高频检测报警中断 */
            FL_FDET_DisableIT_XTHFFail();
          
            SYSCLKState = SYSCLK_NORMAL;
            (void)SelXTHFToPLL(&XTHF_InitParameter, &PLL_InitParameter);
        }
    }
}


